#!/bin/sh
set -e

usage () {
    echo "usage: git modified [-iuqh] [<commit>]" >&2
    echo >&2
    echo "Prints list of files that are locally modified (and exist).  The index is not " >&2
    echo "considered, unless the -i flag is provided." >&2
    echo "" >&2
    echo "If a commit is provided, opens all files that locally exist that have been " >&2
    echo "changed in that commit." >&2
    echo >&2
    echo "This script is ideal for passing all locally modified files into your editor, like:" >&2
    echo '    $ vim `git modified`' >&2
    echo >&2
    echo "Options:" >&2
    echo "-m    Modified files only (excludes untracked files)" >&2
    echo "-i    Consider the index, too" >&2
    echo "-u    Print only files that are unmerged (files with conflicts)" >&2
    echo "-q    Be quiet, only return with 0 exit code when files are modified" >&2
    echo "-h    Show this help" >&2
}

modified=0
index=0
unmerged=0
quiet=0
while getopts miuqh flag; do
    case "$flag" in
        m) modified=1;;
        i) index=1;;
        u) unmerged=1;;
        q) quiet=1;;
        h) usage; exit 2;;
    esac
done
shift $(($OPTIND - 1))

commit=""
if [ $# -ge 1 ]; then
    commit="$1"
fi

#
# git status cheat sheet:
#
# X          Y     Meaning
# -------------------------------------------------
#           [MD]   not updated
# M        [ MD]   updated in index
# A        [ MD]   added to index
# D         [ M]   deleted from index
# R        [ MD]   renamed in index
# C        [ MD]   copied in index
# [MARC]           index and work tree matches
# [ MARC]     M    work tree changed since index
# [ MARC]     D    deleted in work tree
# -------------------------------------------------
# D           D    unmerged, both deleted
# A           U    unmerged, added by us
# U           D    unmerged, deleted by them
# U           A    unmerged, added by them
# D           U    unmerged, deleted by us
# A           A    unmerged, both added
# U           U    unmerged, both modified
# -------------------------------------------------
# ?           ?    untracked
# !           !    ignored
# -------------------------------------------------

status () {
    git status --porcelain | grep -vEe '^.D' | grep -vEe '^D '
}

fix_rename_notation () {
    sed -Ee 's/.* -> (.*)/\1/'
}

make_relative () {
    while read f; do
        git relative-path "$f"
    done
}

modified_in_index () {
    status | cut -c 4- | fix_rename_notation | make_relative
}

modified_unmerged () {
    status | grep -Ee '^(U.|.U)' | cut -c 4- | fix_rename_notation | make_relative
}

modified_only_locally () {
    status | cut -c 2- | grep -Ee '^M' | cut -c 3- | fix_rename_notation | make_relative
}

modified_locally () {
    status | cut -c 2- | grep -vEe '^[ ]' | cut -c 3- | fix_rename_notation | make_relative
}

fail_if_empty () {
    empty=1
    while read line; do
        if [ $quiet -eq 0 ]; then
            echo "$line"
        fi
        empty=0
    done
    test $empty -eq 0
}

if [ -z "$commit" ]; then
    if [ $unmerged -eq 1 ]; then
        modified_unmerged | fail_if_empty
    elif [ $index -eq 1 ]; then
        modified_in_index | fail_if_empty
    elif [ $modified -eq 1 ]; then
        modified_only_locally | fail_if_empty
    else
        modified_locally | fail_if_empty
    fi
else
    TAB="	" # literal tab char
    git log -1 --name-status --pretty=format:"" "$commit" | cut -f2- | rev | cut -d"$TAB" -f1 | rev | make_relative | while read f; do
        if [ -f "$f" ]; then
            echo "$f"
        fi
    done | fail_if_empty
fi
